﻿#region Copyright Statement
//  ------------------------------------------------------------------------------
//  
//    This file is part of The Streambolics Library.
//    Copyright © 2005-2009, Stephan Leclercq & Streambolics
//    All rights reserved.
//  
//    The Streambolics Library is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//  
//    The Streambolics Library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//  
//    You should have received a copy of the GNU General Public License
//    along with Streambolics; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//  
//  ------------------------------------------------------------------------------
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace Streambolics.Hp41s
{
    public delegate double DiadicOperator (double x, double y);
    public delegate double MonadicOperator (double x);

    /// <summary>
    ///     The data registers and operand stack of the HP-41C simulator.
    /// </summary>
    /// <remarks><para>
    ///     This class maintains the values of the data registers and
    ///     operand stack of an HP-41C like calculator. It does not know
    ///     about instructions, program steps, etc.
    ///     It can therefore be used as is to implement a non-programmable
    ///     RPN calculator, or as part of an execution engine.
    /// </para></remarks>

    public class Registers : Data
    {
        private string _Alpha;
        private List<Value> _Registers = new List<Value> (126);

        private bool _Dup;

        public Registers ()
            : base ("Registers")

        {
            _Alpha = String.Empty;
            Size = 100;    
        }

        public int Size
        {
            get
            {
                return _Registers.Count - Reference.Offset;
            }
            set
            {
                if (value < 0 || value > 65536)
                {
                    throw new Exception ("OUT OF RANGE");
                }

                int n = value + Reference.Offset;

                if (n < _Registers.Count)
                {
                    _Registers = _Registers.GetRange (0, n);
                }

                while (_Registers.Count < n)
                {
                    _Registers.Add (NumericalValue.Zero);
                }
            }
        }

        /// <summary>
        ///     The ALPHA register
        /// </summary>
        /// <remarks><para>
        ///     The ALPHA register is used by an HP-41C to perform string
        ///     manipulation. 
        /// </para><para>
        ///     For compatibility reasons, the ALPHA register is limited to 24 characters.
        ///     When trying to store a value longer than 24 characters in the ALPHA register,
        ///     the value will be truncated and only the <b>last</b> 24 characters will
        ///     be kept.
        /// </para><para>
        ///     Assigning a string that begins with ► appends the new string instead
        ///     of replacing it.
        /// </para></remarks>

        [Category ("Support Registers")]
        public string Alpha
        {
            get
            {
                return _Alpha;
            }
            set
            {
                if (value.Length > 1 && value[0] == '►')
                {
                    _Alpha = _Alpha + value.Substring (1);
                }
                else
                {
                    _Alpha = value;
                }
                _Alpha = _Alpha.Last (24);
            }
        }

        public Value this[int idx]
        {
            get
            {
                int n = idx + Reference.Offset;
                if (n < 0 || n >= _Registers.Count)
                {
                    throw new NonexistentException (idx, ReferenceType.Register);
                }
                return _Registers[n];
            }
            set
            {
                int n = idx + Reference.Offset;
                if (n < 0 || n >= _Registers.Count)
                {
                    throw new NonexistentException (idx, ReferenceType.Register);
                }
                _Registers[n] = value;
                _Dup = true;
            }
        }

        /// <summary>
        ///     The X register (top of stack)
        /// </summary>
        /// <remarks><para>
        ///     When simulating the result of instructions, it is usually best
        ///     to call the NewX() method instead of directly assigning to X.
        ///     The NewX() method will handle stack 
        /// </para></remarks>

        [Category ("Operand Stack")]
        public Value X
        {
            get
            {
                return this[Reference.X];
            }
            set
            {
                this[Reference.X] = value;
            }
        }

        [Category ("Operand Stack")]
        public Value Y
        {
            get
            {
                return this[Reference.Y];
            }
            set
            {
                this[Reference.Y] = value;
            }
        }

        [Category ("Operand Stack")]
        public Value Z
        {
            get
            {
                return this[Reference.Z];
            }
            set
            {
                this[Reference.Z] = value;
            }
        }

        [Category ("Operand Stack")]
        public Value T
        {
            get
            {
                return this[Reference.T];
            }
            set
            {
                this[Reference.T] = value;
            }
        }

        [Category ("Support Registers")]
        public Value LastX
        {
            get
            {
                return this[Reference.L];
            }
            set
            {
                this[Reference.L] = value;
            }
        }

        /// <summary>
        ///     Place a new value in the X register.
        /// </summary>
        /// <param name="x">
        ///     The new value for the X register.
        /// </param>
        /// <param name="args">
        ///     The number of stack levels to discard.
        /// </param>
        /// <remarks><para>
        ///     If the args parameter is nonzero, previous values are
        ///     removed from the stack. This is useful to replace the value
        ///     of X by the results of a monadic operator (args = 1) or a
        ///     diadic operator (args = 2.) Moreover, the old value of 
        ///     X is stored in LastX.
        /// </para><para>
        ///     If the args parameter is zero, the new value either replaces
        ///     the existing X value (in which case the previous value of X
        ///     is lost,) or is pushed on the stack (in which case the old value
        ///     of T is lost.)
        /// </para>
        ///     Whether the new value replaces or pushes the old values is
        ///     determined by the previous stack movements: if the last
        ///     stack movement was because of the Enter() method, the new value
        ///     replaces X, otherwise it is pushed.
        /// </remarks>

        public void NewX (Value x, int args)
        {
            if (args > 0)
            {
                LastX = X;
                while (args-- > 1)
                {
                    Y = Z;
                    Z = T;
                }
                _Dup = false;
            }
            if (_Dup)
            {
                T = Z;
                Z = Y;
                Y = X;
            }
            X = x;
        }

        public void NewX (Value x)
        {
            NewX (x, 0);
        }

        public void NewX (double x, int args)
        {
            NewX (new NumericalValue (x), args);
        }

        public void NewX (double x)
        {
            NewX (x, 0);
        }

        /// <summary>
        ///     Duplicates the top of stack.
        /// </summary>
        /// <remarks><para>
        ///     Calling this method is similar to pressing the ENTER
        ///     key on an HP-41C: the X register is duplicated in Y 
        ///     while the stack goes up.
        /// </para><para>
        ///     After a call to Enter(), a call to NewX() will replace
        ///     the value of X instead of pushing a new value.
        /// </para></remarks>

        public void Enter ()
        {
            T = Z;
            Z = Y;
            Y = X;
            _Dup = false;
        }

        /// <summary>
        ///     Applies a diadic numerical operator on the top of the stack.
        /// </summary>
        /// <param name="op">
        ///     The diadic operator to execute.
        /// </param>
        /// <remarks><para>
        ///     After execution, the value of X is replaced by the result,
        ///     and the stack goes down.
        ///     The previous value of X is kept in LastX, the previous value
        ///     of Y is lost.
        /// </para></remarks>

        public void ApplyDiadic (DiadicOperator op)
        {
            NewX (op (X.Numerical, Y.Numerical), 2);
        }
    }
}
